#include <windows.h>

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

#include <boost/program_options.hpp>
namespace po = boost::program_options;

#include "EventTypesFilter.h"
#include "ProcessNameFilter.h"
#include "OutputFiles.h"
#include "FileProcessor.h"


/**
 * This functions prepares all filters based on command-line
 * options, and invokes <i>FileProcessor</i> for each
 * ABPA output file.
 */
void processCommandLine(po::variables_map &vm) {
	// types of events
	EventTypesFilter etf;
	if (vm.count("all") == 0) {
		if (vm.count("file")) etf.setEvent(events::BasicEvent::EVENT_FILE_IO);
		if (vm.count("network")) etf.setEvent(events::BasicEvent::EVENT_NETWORK);
		if (vm.count("memory")) etf.setEvent(events::BasicEvent::EVENT_MEMORY);
		if (vm.count("thread")) etf.setEvent(events::BasicEvent::EVENT_THREADS);
		if (vm.count("window")) etf.setEvent(events::BasicEvent::EVENT_WINDOWS);
		if (vm.count("sync")) etf.setEvent(events::BasicEvent::EVENT_SYNC);
		if (vm.count("misc")) etf.setEvent(events::BasicEvent::EVENT_MISC);
	}

	// names of processes
	ProcessNameFilter pnf;
	if (vm.count("process-name"))
		pnf.addRegexList(vm["process-name"].as<vector <string> >());

	// output format (full or headers only)
	bool headersOnly = (vm.count("headers-only") != 0);

	// final results can be saved to a file
	ofstream outFile;
	if (vm.count("save-to-file")) {
		outFile.open(vm["save-to-file"].as<string>().c_str());
		if (!outFile.is_open())
			throw string("could not write to output file");
	}
	ostream &out = outFile.is_open() ? outFile : cout;

	// prepares a list of ABPA files to process
	OutputFiles of;
	if (vm.count("output-folder")) of.setFolder(vm["output-folder"].as<string>());
	if (vm.count("output-file")) of.addFileList(vm["output-file"].as< vector<string> >());
	vector<string> &allFiles = of.getFilesToProcess();

	// now ready to process the files
	FileProcessor fp(etf, pnf, headersOnly, out);
	for_each(allFiles.begin(), allFiles.end(),
			 boost::bind(&FileProcessor::process, ref(fp), _1));
}


/**
 * This program provides basic tools for parsing ABPA output files. The user
 * specifies which files need to be parsed, and gives formatting options.
 *
 * Please refer to the description of command-line options below.
 */
int main(int argc, char *argv[]) {
	// prepare the list of general oprions
	po::options_description desc("Allowed options");
	desc.add_options()
		("help", "produce this help message")
		("output-folder,O", po::value<string>(), "folder to read ABPA output files from (if not specified - uses the value from registry")
		("output-file,f", po::value< vector<string> >(), "will process only listed files (if not specified, will process all files in folder; if a relative filename is given, will search in output folder according to --output-folder option")
		("process-name,p", po::value< vector<string> >(), "this regex will be used as a filter on the process names")
		("headers-only,h", "will only print general info for each file processed (by default will dump all events)")
		("save-to-file,t", po::value<string>(), "will store the output in the specified file (by default standard output is used)")
	;

	// prepare the list of event-types options, and
	// add to the main list
	po::options_description etypes("Event types");
	etypes.add_options()
		("all,A", "All events (is the default if no other filters are given)")
		("file,F", "File events")
		("network,N", "Network events")
		("memory,M", "Memory events")
		("thread,T", "Thread events")
		("window,W", "Window events")
		("sync,S", "Synchronization events")
		("misc,X", "Misc events")
	;
	desc.add(etypes);

	// parse the configuration
	po::variables_map vm;
	try {
		po::store(po::parse_command_line(argc, argv, desc), vm);
	} catch (po::error&) {
		cerr << "Failed to parse command line\n";
		cerr << desc;
		return 1;
	}
	po::notify(vm);

	// check whether help message was requested
	if (vm.count("help")) {
		cout << desc << "\n";
		return 0;
	}

	// do the work
	try {
		processCommandLine(vm);
	} catch(string &s) {
		cerr << "error: " << s << endl;
		return 1;
	} catch(...) {
		cerr << "unexpected error\n";
		return 1;
	}

	return 0;
}
